#include "main.h"

// socket
int sock;

  /* these are set with keyboard events */
static int movementX = 0, movementY = 0;

  /* dot data */
static int dotCount = 1;
static float dotPos[50][2];
static unsigned char dotColor[50][3];
  /* array of signs */
static int inOwnNet[50];

  /* dot sizes */
static int sizeOwn = 4;
static int sizeOther = 2;

  /* anti-aliasing switch */
static int antiAliasing = 0;

  /* player names */
static char playerName[50][21];

  /* if this switch is off, the player gets a random dot color */
static int customColor = 0;

  /* net connection threshold */
static float maxDistance = 0.3;

static int clientGone[50];

// random dots
static int doRandom = 0;

// sockets stuff
static char hostName[16] = "127.0.0.1";
static int port = 1300;

// POS-string
char posString[200];


int serverIDs[50]; // serverIDs[localid] = serverid
int clientIDs[50]; // clientIDs[serverid] = localid


int numClients;

pthread_t LSTThread;
//pthread_t POSThread;


int main(int argc, char **argv) {
	extern char *optarg;
	extern int optind;
	int opt;
	int color;
	pthread_t socketThread;

	  /* seed random generator */
	srand(time(NULL));

	  /* initialize GLUT */
	glutInit(&argc, argv);
	

	  /* get options */
	while((opt = getopt(argc, argv, "ac:hi:p:r")) != -1) {
		switch(opt) {
		case 'a':
			antiAliasing = 1;
			sizeOwn += 2;
			sizeOther += 2;
			break;
		case 'c':
			customColor = 1;
			sscanf(optarg, "%x", &color);
			dotColor[0][2] = (color & 0x0000ff);
			dotColor[0][1] = (color & 0x00ff00) >> 8;
			dotColor[0][0] = (color & 0xff0000) >> 16;
			break;
		case 'h':
			printHelp(argv[0]);
			exit(0);
			break;
		case 'i':
			strncpy(hostName, optarg, 15);
			break;
		case 'p':
			port = atoi(optarg);
			break;
		case 'r':
			doRandom = 1;
			break;
		}
	}
	if(argc > optind) {
		setName(0, argv[optind]);
		printf("Your player name has been set to %s.\n", playerName[0]);
	} else {
		printf("You must give a name!\n\n");
		printHelp(argv[0]);
		exit(1);
	}
	
	// start the socket thread
	pthread_create(&socketThread, NULL, socketThreadFunction, NULL);
	
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutInitWindowSize(400, 400);
	glutCreateWindow("Dotgame of Community (Client)");

	init();
	timer(0);

	  /* set callback functions for events */
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutKeyboardFunc(keyDown);
	glutSpecialFunc(specialKeyDown);
	glutSpecialUpFunc(specialKeyUp);
	printf("test5\n");
	glutMainLoop();

	return 0;
}

void error(char *msg) {
	perror(msg);
	exit(1);
}

int proc_buffer(char *buffer, char *sendbuffer) {
	char cmd[200];
	char arg1[250], arg2[250], arg3[250], arg4[250], cx[50], cy[50];
	int i, ret;
	float x,y;
	char res[500];
	
	
	printf("DEBUG: Server sends: '%s'\n", buffer);
	
	get_part(buffer, cmd, 0);
	
	if(strncmp(cmd, "NEW", 3)==0) {
		// NEW 1 thomas, get id in arg1, name in arg2
		get_part(buffer, arg1, 1); // id
		get_part(buffer, arg2, 2); // name
		
		// if name == my name, set my ID to arg2
		if(strncmp(arg2, playerName[0], strlen(playerName[0]))==0) {
			serverIDs[0] = atoi(arg1);	// set the serverID of myself to arg1.
			clientIDs[atoi(arg1)] = 0;	// ^ but the other way around
			
			return 3; // to say we can send an LST
		}
		else {	// a new client connected, put him in the lists.
			serverIDs[numClients] = atoi(arg1);
			clientIDs[atoi(arg1)] = numClients;
			
			dotCount++;
			
			dotColor[numClients][0] = rand() % 256;
			dotColor[numClients][1] = rand() % 256;
			dotColor[numClients][2] = rand() % 256;
			
			
			setName(numClients, arg2);	// set the name
			
			printf("New client connected, ID: %i, name: %s.\n", numClients, playerName[numClients]);
		
			numClients++;
		}
		
		strcpy(sendbuffer, "NEW OK \n");
	}
	
	if(strncmp(cmd, "CLI", 3)==0) {
		i = 0;
		ret = 1;
		while(ret) {
		
			ret = get_part_lst(buffer, res, i);
			i++;
			// split this string in:
			// CLI 1 thomas x,y
			get_part(res, arg1, 1); // ID
			get_part(res, arg2, 2); // name
			get_part(res, arg3, 3); // X
			get_part(res, arg4, 4); // Y
			
			if(atoi(arg1) == serverIDs[0]) {
				printf("found me in LST.\n");
				continue;	// It's me
			}
				
			printf("Client, cID: %i, name: %s.\n", numClients, playerName[numClients]);
				
			// If we made it till here
			// it's a new client, not me.
			
			serverIDs[numClients] = atoi(arg1);	// same but the 
			clientIDs[atoi(arg1)] = numClients;	// other way around
			
			dotCount++;
			
			strcpy(playerName[numClients], arg2);
			
			dotPos[numClients][X] = atof(arg3);
			dotPos[numClients][Y] = atof(arg4);
			
			dotColor[numClients][0] = rand() % 256;
			dotColor[numClients][1] = rand() % 256;
			dotColor[numClients][2] = rand() % 256;
			
			
			numClients++;
		}
	
	}
	
	if(strncmp(cmd, "BYE", 3)==0) {
		get_part(buffer, arg1, 1);
		dotPos[clientIDs[atoi(arg1)]][X] = dotPos[clientIDs[atoi(arg1)]][Y] = 0;
		
		// talk to the graphic side: client atoi(arg1) is gone.
		clientGone[atoi(arg1)] = 1;
		
		printf("Client %i (%s) left.\n", clientIDs[atoi(arg1)], playerName[clientIDs[atoi(arg1)]]);
	
	}
	
	if(strncmp(cmd, "POS", 3)==0) {
		get_part(buffer, arg1, 1);	// server ID of moving client in arg1
		get_part(buffer, cx, 2);	// X in cx
		get_part(buffer, cy, 3);	// Y in cy
		
		if(atoi(arg1) == serverIDs[0])
			return; // it's myself, moving.
		
		// change array items
		dotPos[clientIDs[atoi(arg1)]][X] = atof(cx);
		dotPos[clientIDs[atoi(arg1)]][Y] = atof(cy);
	
	}

}
/* This is called directly now
void *POSThreadFunction(void *ptr) {
	char posString[200];
	sprintf(posString, "POS %f,%f \n", dotPos[0][X], dotPos[0][Y]);
	send(sock, posString, strlen(posString), 0);
}
*/


void *LSTThreadFunction(void *ptr) {
	char listString[50];
	sprintf(listString, "LST \n");
	send(sock, listString, strlen(listString), 0);
}

void *socketThreadFunction(void *ptr) {
	struct sockaddr_in addrClient;
	char buffer[200];
	char sendbuffer[200];
	char welcomeString[200];
	int received = 0;
	
	numClients = 1; // 1 after myself

	// create the socket
	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sock < 0) {
		error("Failed to create the socket");
	}
	
	 
	// the client's sockaddr_in structure
	memset(&addrClient, 0, sizeof(addrClient));
	addrClient.sin_family = AF_INET;
	addrClient.sin_addr.s_addr = inet_addr(hostName);
	addrClient.sin_port = htons(port);
	
	// the connection
	if(connect(sock, (struct sockaddr *)&addrClient, sizeof(addrClient)) < 0) {
		error("Failed to connect to the server");
	}
	
	
	// create and send the HI-string
	sprintf(welcomeString, "HI %s \n", playerName[0]);
	send(sock, welcomeString, strlen(welcomeString), 0);
	
	while(1) {
		memset(buffer, '\0', sizeof(buffer));
		if((received = recv(sock, buffer, 200, 0)) < 0) {
			error("Failed to receive more data from server");
		}
		

		
		memset(sendbuffer, '\0', sizeof(sendbuffer));
		if(proc_buffer(buffer, sendbuffer) == 3) { // we can send an LST
			pthread_create(&LSTThread, NULL, LSTThreadFunction, NULL);
		}
		
		send(sock, sendbuffer, strlen(sendbuffer), 0);
	}


}

  /* initialize stuff */
void init(void) {
	int i;

	  /* initialize dot position and color */
	dotPos[0][0] = dotPos[0][1] = 0;
	if(!customColor) {
		dotColor[0][0] = rand() % 256;
		dotColor[0][1] = rand() % 256;
		dotColor[0][2] = rand() % 256;
	}


	if(doRandom) {
		  /* distribube other dots randomly (only temporary) */
		dotCount = 20;
		for(i=1; i<20; i++) {
			dotPos[i][0] = (rand()%201)/100.0 - 1;
			dotPos[i][1] = (rand()%201)/100.0 - 1;
			dotColor[i][0] = rand() % 256;
			dotColor[i][1] = rand() % 256;
			dotColor[i][2] = rand() % 256;
		}
	}

	glLineStipple(2, 0x5555);
	glClearColor(0, 0, 0, 0);

	  /* enable anti-aliasing */
	if(antiAliasing) {
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND);
		glEnable(GL_POINT_SMOOTH);
	}

	  /* define function to be called upon termination */
	atexit(end);
}

void end(void) {
	  /* send a BYE to the server */
	send(sock, "BYE \n", strlen("BYE \n"), 0);
	exit(0);
}

  /* rendering function */
void display(void) {
	int i;

	glClear(GL_COLOR_BUFFER_BIT);

	  /* draw borders */
	glColor3f(1, 1, 1);
	glEnable(GL_LINE_STIPPLE);
	glBegin(GL_LINE_LOOP);
		glVertex3f(-1, -1, 0);
		glVertex3f(1, -1, 0);
		glVertex3f(1, 1, 0);
		glVertex3f(-1, 1, 0);
	glEnd();
	glDisable(GL_LINE_STIPPLE);

	drawNets();

	  /* draw own dot with white frame */
	glPointSize(sizeOwn+2);
	glColor3ub(255, 255, 255);
	glBegin(GL_POINTS);
		glVertex2fv(dotPos[0]);
	glEnd();
	glPointSize(sizeOwn);
	glBegin(GL_POINTS);
		glColor3ubv(dotColor[0]);
		glVertex2fv(dotPos[0]);
	glEnd();
	  /* draw frames around dots in the own network */
	glPointSize(sizeOther+2);
	glBegin(GL_POINTS);
	glColor3ub(200, 200, 200);
	for(i=1; i<dotCount; i++) {
		if(inOwnNet[i] && !clientGone[i]) {
			glVertex2fv(dotPos[i]);
		}
	}
	glEnd();
	  /* draw other dots */
	glPointSize(sizeOther);
	glBegin(GL_POINTS);
	for(i=1; i<dotCount; i++) {
		if(clientGone[serverIDs[i]]) continue;
		glColor3ubv(dotColor[i]);
		glVertex2fv(dotPos[i]);
	}
	glEnd();
	glutSwapBuffers();
}

  /* window resizing function */
void reshape(int w, int h) {
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	  /* always use maximum quadratic space */
	if(w > h) {
		gluOrtho2D(-(float)w/h, (float)w/h, -1, 1);
	} else {
		gluOrtho2D(-1, 1, -(float)h/w, (float)h/w);
	}
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

  /* normal key-down function */
void keyDown(unsigned char key, int x, int y) {
	switch(key) {
	  /* + and - set the net distance threshold */
	case '+':
		maxDistance += 0.02;
		if(maxDistance > 1) maxDistance = 1;
		break;
	case '-':
		maxDistance -= 0.02;
		if(maxDistance < 0) maxDistance = 0;
		break;
	case 27:
	case 'q':
		end();
		break;
	}
}

  /* special key-down function (for arrow keys etc.) */
void specialKeyDown(int key, int x, int y) {
	switch(key) {
	case GLUT_KEY_UP:
		movementY = 1;
		break;
	case GLUT_KEY_DOWN:
		movementY = -1;
		break;
	case GLUT_KEY_RIGHT:
		movementX = 1;
		break;
	case GLUT_KEY_LEFT:
		movementX = -1;
		break;
	}
}

  /* special key-up function (for arrow keys etc.) */
void specialKeyUp(int key, int x, int y) {
	switch(key) {
	case GLUT_KEY_UP:
	case GLUT_KEY_DOWN:
		movementY = 0;
		break;
	case GLUT_KEY_RIGHT:
	case GLUT_KEY_LEFT:
		movementX = 0;
		break;
	}
}

  /* main timer for movement and redrawing */
void timer(int value) {
	  /* set timer event callback to this very function */
	glutTimerFunc(20, timer, 0);

	  /* move dot */
	dotPos[0][0] += movementX*0.01;
	dotPos[0][1] += movementY*0.01;


	  /* don't move outside of borders */
	if(dotPos[0][0] > 1) {
		dotPos[0][0] = 1;
	} else if(dotPos[0][0] < -1) {
		dotPos[0][0] = -1;
	}
	if(dotPos[0][1] > 1) {
		dotPos[0][1] = 1;
	} else if(dotPos[0][1] < -1) {
		dotPos[0][1] = -1;
	}

	  /* redraw screen */
	glutPostRedisplay();
	
	// if there is movement, send a POS to the server (by __NOT__ starting a new thread)
	if(movementX != 0 || movementY != 0) {
		sprintf(posString, "POS %f,%f \n", dotPos[0][X], dotPos[0][Y]);
		send(sock, posString, strlen(posString), 0);
	}
}

  /* What do you think this function does? */
void printHelp(char *filename) {
	printf("Usage:\n\
  %s [options] name\n\
Options:\n\
  -a:  turn on anti-aliasing\n\
  -c color:  set own dot color in hex (for example 0x4f04ab or ffaabb)\n\
  -h:  print this\n\
  -i ip:  specify server IP\n\
  -p port:  specify server port\n\
  -r:  randomly draw dots on the screen\n", filename);
}

  /* checks and sets the player's name */
void setName(int num, char *name) {
	int i, assignI = 0;

	for(i=0; i<20; i++) {
		if((name[i] >= '0' && name[i] <= '9') || \
		  (name[i] >= 'A' && name[i] <= 'Z') || \
		  (name[i] >= 'a' && name[i] <= 'z')) {
			playerName[num][assignI] = name[i];
			assignI ++;
		} else if(name[i] == '\0') {
			break;
		}
	}
	playerName[num][assignI+1] = '\0';
}

  /* draws lines between close dots */
void drawNets(void) {
	int i, j;
	int newMembersFound = 0;
	float distance, color;

	memset(inOwnNet, 0, sizeof(int)*dotCount);
	  /* look for immediate neighbors of player */
	for(j=0; j<dotCount; j++) {
		distance = sqrt(pow(dotPos[0][0] - dotPos[j][0], 2) + \
		  pow(dotPos[0][1] - dotPos[j][1], 2));
		if(distance < maxDistance) {
			inOwnNet[j] = 1;
			newMembersFound = 1;
		}
	}
	  /* look for indirect neighbors */
	while(newMembersFound) {
		newMembersFound = 0;
		for(i=0; i<dotCount; i++) {
			if(!inOwnNet[i]) continue;
			if(clientGone[serverIDs[i]]) continue;
			for(j=0; j<dotCount; j++) {
				if(inOwnNet[j]) continue;
				distance = sqrt(pow(dotPos[i][0]-dotPos[j][0], \
				  2) + pow(dotPos[i][1]-dotPos[j][1], 2));
				if(distance < maxDistance) {
					inOwnNet[j] = 1;
					newMembersFound = 1;
				}
			}
		}
	}
	glBegin(GL_LINES);
	for(i=0; i<dotCount; i++){
		for(j=i; j<dotCount; j++) {
			if(clientGone[serverIDs[j]]) continue;
			distance = sqrt(pow(dotPos[i][0] - dotPos[j][0], 2) + \
			  pow(dotPos[i][1] - dotPos[j][1], 2));
			if(distance < maxDistance) {
				color = 1 - 0.9*distance/maxDistance;
				if(inOwnNet[i]) {
					glColor3f(color, color, color);
				} else {
					glColor3f(color, 0, 0);
				}
				glVertex2fv(dotPos[i]);
				glVertex2fv(dotPos[j]);
			}
		}
	}
	glEnd();
}
